package eu.catengine.scene;

import java.util.ArrayList;
import java.util.Stack;

import eu.catengine.common.iface.IDisposable;
import eu.catengine.primitive.Point;
import eu.catengine.renderable.Renderable;

/**
 * Klasa która przechowuje wszystkie elementy, które zostaną wyświetlone na
 * ekranie.
 * 
 * @author mbienkowski
 */
public class Layer implements IDisposable
{
    ArrayList<Renderable> mObjectList;
    ArrayList<Layer> mLayerList;
    LayerTree mRoot;
    Layer mParent;
    String mName;

    /**
     * Tworzy warstwę.
     * 
     * @param name
     *            <code>String</code> Nazwa warstwy.
     * @param parent
     *            <code>Layer</code> Warstwa nadrzędna.
     * @param root
     *            <code>LayerTree</code> Warstwa początkowa.
     */
    protected Layer(String name, Layer parent, LayerTree root)
    {
	mObjectList = new ArrayList<Renderable>();
	mLayerList = new ArrayList<Layer>();
	mName = name;
	mRoot = root;
	mParent = parent;
    }

    /**
     * Tworzy i dodaje warstwę.
     * 
     * @param name
     *            <code>String</code> Nazwa warstwy.
     * @return <code>Layer</code> Utworzona warstwa lub obiekt warstwy w
     *         przypadku, gdy warstwa o takiej samej nazwie istnieje.
     */
    public Layer addLayer(String name)
    {
	if (mRoot.mTreeLayerMap.containsKey(name))
	{
	    return mRoot.mTreeLayerMap.get(name);
	}

	Layer subLayer = new Layer(name, this, mRoot);

	mLayerList.add(subLayer);
	mRoot.mTreeLayerMap.put(name, this);

	return subLayer;
    }

    /**
     * Pobiera warstwę o nazwie podanej jako parametr.
     * 
     * @param name
     *            <code>String</code> Nazwa szukanej warstwy.
     * @return <code>Layer</code> Wyszukiwana warstwa lub wartość null.
     */
    public Layer getLayer(String name)
    {
	return mRoot.mTreeLayerMap.get(name);
    }

    /**
     * Pobiera rodzica danej warstwy
     * 
     * @return <code>Layer</code> Warstwa nadrzędna lub null w przypadku, gdy
     *         taka nie występuje.
     */
    public Layer getParent()
    {
	return mParent;
    }

    /**
     * Usuwa warstwę oraz wszystkie warstwy wewnetrzne. <br>
     */
    public void deleteLayer()
    {
	ArrayList<Layer> layers = getAllLayersDFS();

	Layer lay;
	// Najpierw usuwam wszystkie obiekty z usuwanych warstw

	for (int i = 0; i < layers.size(); i++)
	{
	    lay = layers.get(i);
	    mRoot.mLayerList.remove(lay);

	    for (int j = 0; j < lay.mObjectList.size(); i++)
	    {
		deleteObject(lay.mObjectList.get(j));
	    }
	}

	// Ostatecznie usuwam warstwy

	lay = layers.get(0).getParent();

	if (lay == null)
	{
	    mRoot.mLayerList.remove(layers.get(0));
	}
	else
	{
	    lay.mLayerList.remove(layers.get(0));
	}
    }

    // Elementy warstw

    /**
     * Dodaje obiekt zawarty w parametrze do warstwy.
     * 
     * @param object
     *            <code>Renderable</code> Element do dodania.
     * @return <code>Renderable</code> Dodawany obiekt lub null w przypadku, gdy
     *         obiekt o takiej samej nazwie istnieje.
     */
    public Renderable addObject(Renderable object)
    {
	if (mRoot.mTreeObjectMap.containsKey(object.getName()))
	{
	    return null;
	}

	object.setStartTime(mRoot.mParentScene.getElapsedTime());
	mObjectList.add(object);

	if (object.isUpdateNeeded())
	{
	    mRoot.mTreeUpdateList.add(object);
	}

	mRoot.mTreeObjectMap.put(object.getName(), object);
	mRoot.mTreeObjectLayerMap.put(object.getName(), this);
	mRoot.updateRenderList();

	return object;
    }

    /**
     * Pobiera obiekt o nazwie podanej jako parametr.
     * 
     * @param name
     *            <code>String</code> Nazwa obiektu.
     * @return <code>Renderable</code> Poszukiwany obiekt lub null w przypadku, gdy
     *         taki nie wystepuje.
     */
    public Renderable getObject(String name)
    {
	return mRoot.mTreeObjectMap.get(name);
    }

    /**
     * Pobiera warstwę w której wystepuje obiekt o nazwie podanej jako parametr.
     * 
     * @param name
     *            <code>String</code> Nazwa obiektu.
     * @return <code>Layer</code> Poszukiwana warstwa lub null w przypadku, gdy
     *         taki obiekt nie wystepuje.
     */
    public Layer getObjectLayer(String name)
    {
	return mRoot.mTreeObjectLayerMap.get(name);
    }

    /**
     * Usuwa obiekt.
     * 
     * @param name
     *            <code>String</code> Nazwa obiektu.
     */
    public void deleteObject(String name)
    {
	Renderable object = getObject(name);
	Layer layer = getObjectLayer(name);

	mRoot.mTreeObjectMap.remove(name);
	mRoot.mTreeUpdateList.remove(object);
	mRoot.mTreeRenderList.remove(object);
	mRoot.mTreeObjectLayerMap.remove(name);

	layer.mObjectList.remove(object);
    }

    /**
     * Usuwa obiekt.
     * 
     * @param object
     *            <code>Renderable</code> Obiekt do usuniecia.
     */
    public void deleteObject(Renderable object)
    {
	String name = object.getName();
	Layer layer = getObjectLayer(name);

	mRoot.mTreeObjectMap.remove(name);
	mRoot.mTreeUpdateList.remove(object);
	mRoot.mTreeRenderList.remove(object);
	mRoot.mTreeObjectLayerMap.remove(name);

	layer.mObjectList.remove(object);

	object.dispose();
	object = null;
    }

    // Pozostale

    /**
     * Zwraca wszystkie obiekty danej warstwy. <br>
     * Nie zwraca elementów z warstw wewnętrznych.
     * 
     * @return <code>ArrayList<Renderable></code> Lista obiektów typu
     *         <code>Renderable</code>.
     */
    public ArrayList<Renderable> getObjects()
    {
	return mObjectList;
    }

    /**
     * Zwraca wszystkie obiekty danej warstwy oraz elementy z
     * warstw wewnętrznych.
     * 
     * @return <code>ArrayList<Renderable></code> Lista obiektów typu
     *         <code>Renderable</code>.
     */
    protected ArrayList<Renderable> getAllObjectDFS()
    {
	Stack<Layer> stack = new Stack<Layer>();
	ArrayList<Renderable> allObjectList = new ArrayList<Renderable>();

	for (int i = mLayerList.size() - 1; i >= 0; i--)
	{
	    stack.add(mLayerList.get(i));
	}

	allObjectList.addAll(this.mObjectList);

	while (!stack.isEmpty())
	{
	    Layer tmp = stack.pop(); // Pobieram warstwe ze stosu

	    allObjectList.addAll(tmp.mObjectList); // Dodaje wszystkie elementy
						   // pobranej warstwy do listy

	    for (int i = tmp.mLayerList.size() - 1; i >= 0; i--) // Dodaje
								 // pozostale
								 // warstwy do
								 // stosu
	    {
		stack.add(tmp.mLayerList.get(i));
	    }
	}

	return allObjectList;
    }

    /**
     * Zwraca wszystkie warstwy potomne.
     * 
     * @return <code>ArrayList<Layer></code> Lista obiektów typu <code>Layer</code>.
     */
    protected ArrayList<Layer> getAllLayersDFS()
    {
	Stack<Layer> stack = new Stack<Layer>();
	ArrayList<Layer> allLayerList = new ArrayList<Layer>();

	stack.add(this);

	while (!stack.isEmpty())
	{
	    Layer tmp = stack.pop(); // Pobieram warstwe ze stosu
	    allLayerList.add(tmp);

	    for (int i = tmp.mLayerList.size() - 1; i >= 0; i--)
	    {
		stack.add(tmp.mLayerList.get(i));
	    }
	}

	return allLayerList;
    }
    
    /**
     * Zwraca element który występuje w danym punkcie i jednocześnie jest
     * najwyżej w warstwie sceny.
     * 
     * @param point
     *            <code>Point</code> Punkt poszukiwań obiektu.
     * @return <code>Renderable</code> Odnaleziony element lub null w przypadku braku
     *         elementu.
     */
    public Renderable getObjectAtPoint(Point point)
    {
	for (int i = mObjectList.size() - 1; i >= 0; i--)
	{
	    if (mObjectList.get(i).isAtPoint(point))
	    {
		return mObjectList.get(i);
	    }
	}

	return null;
    }

    /**
     * Zwraca wszystkie elementy znajdujące się w danym punkcie.
     * 
     * @param point
     *            <code>Point</code> Punkt poszukiwań obiektów.
     * @return <code>ArrayList<Renderable></code> Lista znalezionych obiektów.
     */
    public ArrayList<Renderable> getObjectsAtPoint(Point point)
    {
	ArrayList<Renderable> list = new ArrayList<Renderable>();

	for (int i = mObjectList.size() - 1; i >= 0; i--)
	{
	    if (mObjectList.get(i).isAtPoint(point))
	    {
		list.add(mObjectList.get(i));
	    }
	}

	return list;
    }


    @Override
    public void dispose()
    {
	deleteLayer();

	mObjectList = null;
	mLayerList = null;
	mRoot = null;
	mParent = null;
	mName = null;
    }
}
